package me.grax.jbytemod.utils;

import org.objectweb.asm.Handle;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.FrameNode;
import org.objectweb.asm.tree.IincInsnNode;
import org.objectweb.asm.tree.InvokeDynamicInsnNode;
import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.LineNumberNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MultiANewArrayInsnNode;
import org.objectweb.asm.tree.TableSwitchInsnNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.VarInsnNode;

import me.grax.jbytemod.JByteMod;
import me.grax.jbytemod.res.Option;
import me.lpk.util.OpUtils;

public class InstrUtils {

  public static Option primColor = JByteMod.ops.get("primary_color");
  public static Option secColor = JByteMod.ops.get("secondary_color");

  public static String toString(AbstractInsnNode ain) {
    String opc = TextUtils.toBold(OpUtils.getOpcodeText(ain.getOpcode()).toLowerCase()) + " ";
    switch (ain.getType()) {
    case AbstractInsnNode.LABEL:
      opc = TextUtils.toLight("label " + OpUtils.getLabelIndex((LabelNode) ain));
      break;
    case AbstractInsnNode.LINE:
      opc = TextUtils.toLight("line " + ((LineNumberNode) ain).line);
      break;
    case AbstractInsnNode.FIELD_INSN:
      FieldInsnNode fin = (FieldInsnNode) ain;
      opc += getDisplayType(TextUtils.escape(fin.desc), true) + " " + getDisplayClassRed(TextUtils.escape(fin.owner)) + "." + fin.name;
      break;
    case AbstractInsnNode.METHOD_INSN:
      MethodInsnNode min = (MethodInsnNode) ain;
      if(min.desc.contains(")")) {
      opc += getDisplayType(min.desc.split("\\)")[1], true);
      } else {
        opc += min.desc;
      }
      opc += " " + getDisplayClassRed(TextUtils.escape(min.owner)) + "." + TextUtils.escape(min.name)
          + "(" + getDisplayArgs(TextUtils.escape(min.desc)) + ")";
      break;
    case AbstractInsnNode.VAR_INSN:
      VarInsnNode vin = (VarInsnNode) ain;
      opc += vin.var;
      break;
    case AbstractInsnNode.TYPE_INSN:
      TypeInsnNode tin = (TypeInsnNode) ain;
      String esc = TextUtils.escape(tin.desc);
      if (esc.endsWith(";") && esc.startsWith("L")) {
        opc += TextUtils.addTag(esc, "font color=" + primColor.getString());
      } else {
        opc += getDisplayClass(esc);
      }
      break;
    case AbstractInsnNode.MULTIANEWARRAY_INSN:
      MultiANewArrayInsnNode mnin = (MultiANewArrayInsnNode) ain;
      opc += mnin.dims + " " + getDisplayType(TextUtils.escape(mnin.desc), true);
      break;
    case AbstractInsnNode.JUMP_INSN:
      JumpInsnNode jin = (JumpInsnNode) ain;
      opc += OpUtils.getLabelIndex(jin.label);
      break;
    case AbstractInsnNode.LDC_INSN:
      LdcInsnNode ldc = (LdcInsnNode) ain;
      opc += TextUtils.addTag(ldc.cst.getClass().getSimpleName(), "font color=" + primColor.getString()) + " ";
      if (ldc.cst instanceof String)
        opc += TextUtils.addTag("\"" + TextUtils.escape(ldc.cst.toString()) + "\"", "font color=#559955");
      else {
        opc += ldc.cst.toString();
      }
      break;
    case AbstractInsnNode.INT_INSN:
      opc += OpUtils.getIntValue(ain);
      break;
    case AbstractInsnNode.IINC_INSN:
      IincInsnNode iinc = (IincInsnNode) ain;
      opc += iinc.var + " " + iinc.incr;
      break;
    case AbstractInsnNode.FRAME:
      FrameNode fn = (FrameNode) ain;
      opc = TextUtils.toLight(OpUtils.getFrameType(fn.type).toLowerCase() + " " + fn.local.size() + " " + fn.stack.size());
      break;
    case AbstractInsnNode.TABLESWITCH_INSN:
      TableSwitchInsnNode tsin = (TableSwitchInsnNode) ain;
      if (tsin.dflt != null) {
        opc += TextUtils.addTag("L" + OpUtils.getLabelIndex(tsin.dflt), "font color=" + secColor.getString());
      }
      if (tsin.labels.size() < 20) {
        for (LabelNode l : tsin.labels) {
          opc += " " + TextUtils.addTag("L" + OpUtils.getLabelIndex(l), "font color=" + primColor.getString());
        }
      } else {
        opc += " " + TextUtils.addTag(tsin.labels.size() + " cases", "font color=" + primColor.getString());
      }
      break;
    case AbstractInsnNode.INVOKE_DYNAMIC_INSN:
      InvokeDynamicInsnNode idin = (InvokeDynamicInsnNode) ain;
      Object[] arr = idin.bsmArgs;
      if (arr.length > 1) {
        Object o = arr[1];
        if (o instanceof Handle) {
          Handle h = (Handle) o;
          opc += getDisplayType(h.getDesc().split("\\)")[1], true) + " " + getDisplayClassRed(TextUtils.escape(h.getOwner())) + "."
              + TextUtils.escape(h.getName()) + "(" + getDisplayArgs(TextUtils.escape(h.getDesc())) + ")";

        }
      } else {
        opc += TextUtils.addTag(TextUtils.escape(idin.name), "font color=" + primColor.getString()) + " " + TextUtils.escape(idin.desc);
      }
      break;
    }
    return opc;
  }

  public static String getDisplayClass(String str) {
    String[] spl = str.split("/");
    if (spl.length > 1) {
      return TextUtils.addTag(spl[spl.length - 1], "font color=" + primColor.getString());
    }
    return TextUtils.addTag(str, "font color=" + primColor.getString());
  }

  public static String getDisplayClassRed(String str) {
    String[] spl = str.split("/");
    if (spl.length > 1) {
      return TextUtils.addTag(spl[spl.length - 1], "font color=" + secColor.getString());
    }
    return TextUtils.addTag(str, "font color=" + secColor.getString());
  }

  public static String getDisplayArgs(String rawType) {
    if(!rawType.contains(")")) return "?";
    return getDisplayType(rawType.split("\\)")[0].substring(1), true);
  }

  public static String getDisplayType(String rawType, boolean tag) {
    String result = "";
    String tmpArg = "";
    String argSuffix = "";
    boolean isFullyQualifiedClass = false;
    for (char chr : rawType.toCharArray()) {
      if (isFullyQualifiedClass) {
        if (chr == ';') {
          String[] spl = tmpArg.split("/");
          result += spl[spl.length - 1] + argSuffix + ", ";
          argSuffix = "";
          isFullyQualifiedClass = false;
          tmpArg = "";
        } else {
          tmpArg += chr;
        }
      } else if (chr == '[') {
        argSuffix += "[]";
      } else if (chr == 'L') {
        isFullyQualifiedClass = true;
      } else {
        if (chr == 'Z') {
          result += "boolean";
        } else if (chr == 'B') {
          result += "byte";
        } else if (chr == 'C') {
          result += "char";
        } else if (chr == 'S') {
          result += "short";
        } else if (chr == 'I') {
          result += "int";
        } else if (chr == 'J') {
          result += "long";
        } else if (chr == 'F') {
          result += "float";
        } else if (chr == 'D') {
          result += "double";
        } else if (chr == 'V') {
          result += "void";
        } else {
          isFullyQualifiedClass = true;
          continue;
        }

        result += argSuffix;
        argSuffix = "";
        result += ", ";
      }
    }

    if (tmpArg.length() != 0) {
      String[] spl = tmpArg.split("/");
      result += spl[spl.length - 1] + argSuffix + ", ";
    }

    if (result.length() >= 2) {
      result = result.substring(0, result.length() - 2);
    }
    if (tag)
      return TextUtils.addTag(result, "font color=" + primColor.getString());
    return result;
  }

  public final static String getDisplayAccess(int var1) {
    String var2 = "";
    if ((var1 & 1) != 0) {
      var2 = var2 + "public ";
    }

    if ((var1 & 2) != 0) {
      var2 = var2 + "private ";
    }

    if ((var1 & 4) != 0) {
      var2 = var2 + "protected ";
    }

    if ((var1 & 8) != 0) {
      var2 = var2 + "static ";
    }

    if ((var1 & 16) != 0) {
      var2 = var2 + "final ";
    }

    if ((var1 & 1024) != 0) {
      var2 = var2 + "abstract ";
    }
    if (var2.length() > 0) {
      var2 = var2.substring(0, var2.length() - 1);
    }

    return var2;
  }

  public static String toEasyString(AbstractInsnNode ain) {
    String opc = OpUtils.getOpcodeText(ain.getOpcode()).toLowerCase() + " ";
    switch (ain.getType()) {
    case AbstractInsnNode.LABEL:
      opc = "label " + OpUtils.getLabelIndex((LabelNode) ain);
      break;
    case AbstractInsnNode.LINE:
      opc = "line " + ((LineNumberNode) ain).line;
      break;
    case AbstractInsnNode.FIELD_INSN:
      FieldInsnNode fin = (FieldInsnNode) ain;
      opc += getDisplayType(fin.desc, false) + " " + getDisplayClassEasy(fin.owner) + "." + fin.name;
      break;
    case AbstractInsnNode.METHOD_INSN:
      MethodInsnNode min = (MethodInsnNode) ain;
      opc += getDisplayType(min.desc.split("\\)")[1], false) + " " + getDisplayClassEasy(min.owner) + "." + min.name + "("
          + getDisplayArgsEasy(min.desc) + ")";
      break;
    case AbstractInsnNode.VAR_INSN:
      VarInsnNode vin = (VarInsnNode) ain;
      opc += vin.var;
      break;
    case AbstractInsnNode.MULTIANEWARRAY_INSN:
      MultiANewArrayInsnNode mnin = (MultiANewArrayInsnNode) ain;
      opc += mnin.dims + " " + getDisplayType(mnin.desc, false);
      break;
    case AbstractInsnNode.TYPE_INSN:
      TypeInsnNode tin = (TypeInsnNode) ain;
      String esc = tin.desc;
      if (esc.endsWith(";") && esc.startsWith("L")) {
        opc += esc;
      } else {
        opc += getDisplayClassEasy(esc);
      }
      break;
    case AbstractInsnNode.JUMP_INSN:
      JumpInsnNode jin = (JumpInsnNode) ain;
      opc += OpUtils.getLabelIndex(jin.label);
      break;
    case AbstractInsnNode.LDC_INSN:
      LdcInsnNode ldc = (LdcInsnNode) ain;
      opc += ldc.cst.getClass().getSimpleName() + " ";
      if (ldc.cst instanceof String)
        opc += "\"" + ldc.cst.toString() + "\"";
      else {
        opc += ldc.cst.toString();
      }
      break;
    case AbstractInsnNode.INT_INSN:
      opc += OpUtils.getIntValue(ain);
      break;
    case AbstractInsnNode.IINC_INSN:
      IincInsnNode iinc = (IincInsnNode) ain;
      opc += iinc.var + " " + iinc.incr;
      break;
    case AbstractInsnNode.FRAME:
      FrameNode fn = (FrameNode) ain;
      opc = OpUtils.getOpcodeText(fn.type).toLowerCase() + " " + fn.local.size() + " " + fn.stack.size();
      break;
    case AbstractInsnNode.TABLESWITCH_INSN:
      TableSwitchInsnNode tsin = (TableSwitchInsnNode) ain;
      if (tsin.dflt != null) {
        opc += "L" + OpUtils.getLabelIndex(tsin.dflt);
      }
      if (tsin.labels.size() < 20) {
        for (LabelNode l : tsin.labels) {
          opc += " " + "L" + OpUtils.getLabelIndex(l);
        }
      } else {
        opc += " " + tsin.labels.size() + " cases";
      }
      break;
    case AbstractInsnNode.INVOKE_DYNAMIC_INSN:
      InvokeDynamicInsnNode idin = (InvokeDynamicInsnNode) ain;
      opc += idin.name + " " + idin.desc;
      break;
    }
    return opc;
  }

  public static String getDisplayClassEasy(String str) {
    String[] spl = str.split("/");
    if (spl.length > 1) {
      return spl[spl.length - 1];
    }
    return str;
  }

  public static String getDisplayArgsEasy(String rawType) {
    return getDisplayType(rawType.split("\\)")[0].substring(1), false);
  }
}
